﻿// output.h
#pragma once

#include <ostream>     // std::ostream
#include <tuple>       // std::tuple_size/get
#include <type_traits> // std::false_type/true_type/decay_t/is_same_v
#include <utility>     // std::declval/pair

namespace xy
{

namespace details
{

/**
 * @brief 如果生成此结构，说明不是 pair
 *
 * @tparam T
 */
template <typename T> struct is_pair : std::false_type
{
};

/**
 * @brief 对 is_pair 的模板特化 std::pair<T, U>，如果生成此结构，说明传入的是 pair
 *
 * @tparam T
 * @tparam U
 */
template <typename T, typename U> struct is_pair<std::pair<T, U>> : std::true_type
{
};

// 将 T 作为模板，如果它是 pair，则会进入特化版本；否则进入非特化版本。注意内联变量只在 C++17 中有效
template <typename T> inline constexpr bool is_pair_v = is_pair<T>::value;

/**
 * @brief 于判断 T 是否具有 stream 输出
 *
 * @tparam T
 */
template <typename T> struct has_output_function
{
    /**
     * @brief std::declval 返回 std::ostream & 的右值引用，如果 U 具有 stream 输出，则会产生此函数，返回 std::true_type.
     *
     * @tparam U
     * @param[in] ptr
     * @return decltype(std::declval<std::ostream&>() << *ptr, std::true_type())
     */
    template <class U> static auto output(U *ptr) -> decltype(std::declval<std::ostream &>() << *ptr, std::true_type());

    /**
     * @brief 否则会产生此函数，返回 std::false_type.
     *
     * @tparam U
     * @param[in] ...
     * @return std::false_type
     */
    template <class U> static std::false_type output(...);

    // 调用 output 通过返回值判断类型
    static constexpr bool value = decltype(output<T>(nullptr))::value;
};

// 将 T 作为模板传入，检测是否有 output 函数。注意内联变量只在 C++17 中有效
template <typename T> inline constexpr bool has_output_function_v = has_output_function<T>::value;

/**
 * @brief 对于容器 Cont，如果它具有 key_type 键值对，并且它的值为 pair 类型的容器，就会使用此输出函数.
 *
 * @tparam T
 * @tparam Cont
 * @param[in] os
 * @param[in] element
 * @return decltype(std::declval<typename Cont::key_type>(), os)
 */
template <typename T, typename Cont>
inline auto output_element(std::ostream &os, const T &element, const Cont &, const std::true_type)
    -> decltype(std::declval<typename Cont::key_type>(), os);

/**
 * @brief 针对其它所有容器.
 *
 * @tparam T
 * @tparam Cont
 * @param[in] os
 * @param[in] element
 * @param[in] ...
 * @return decltype(os)
 */
template <typename T, typename Cont>
inline auto output_element(std::ostream &os, const T &element, const Cont &, ...) -> decltype(os);

/**
 * @brief 输出 tuple 类型.
 *
 * @tparam IDX
 * @tparam MAX
 * @tparam Args
 */
template <int IDX, int MAX, typename... Args> struct print_tuple
{
    static void print(std::ostream &os, const std::tuple<Args...> &t)
    {
        // std::get 获得 tuple 中对应 IDX 索引的元素
        os << std::get<IDX>(t) << (IDX + 1 == MAX ? "" : ", ");
        print_tuple<IDX + 1, MAX, Args...>::print(os, t);
    }
};

/**
 * @brief 偏特化
 *
 * @tparam MAX
 * @tparam Args
 */
template <int MAX, typename... Args> struct print_tuple<MAX, MAX, Args...>
{
    static void print(std::ostream &os, const std::tuple<Args...> &t)
    {
    }
};

} // namespace details

} // namespace xy

/**
 * @brief 为 pair 设定的输出函数.
 *
 * @tparam T
 * @tparam U
 * @param[in] os
 * @param[in] pr
 * @return std::ostream&
 */
template <typename T, typename U> inline std::ostream &operator<<(std::ostream &os, const std::pair<T, U> &pr);

/**
 * @brief 如果 T 没有输出函数，就会定义此函数.
 *
 * @tparam T
 * @tparam typename
 * @param[in] os
 * @param[in] container
 * @return decltype(container.begin(), container.end(), os)
 */
template <typename T>
    requires(!xy::details::has_output_function_v<T>)
inline auto operator<<(std::ostream &os, const T &container) -> decltype(container.begin(), container.end(), os)
{
    using std::decay_t;
    using std::is_same_v;

    // 判断容器元素是否是 char，只要不是，就输出 {
    using element_type = decay_t<decltype(*container.begin())>;
    constexpr bool is_char_v = is_same_v<element_type, char>;
    if constexpr (!is_char_v)
        os << "{ ";

    if (!container.empty())
    {
        auto end = container.end();
        bool on_first_element = true;
        for (auto it = container.begin(); it != end; ++it)
        {
            if constexpr (is_char_v)
            {
                if (*it == '\0')
                    break;
            }
            if constexpr (!is_char_v)
            {
                if (!on_first_element)
                    os << ", ";
                else
                    on_first_element = false;
            }

            // 根据类型是否为 char 来调用输出模板函数
            xy::details::output_element(os, *it, container, xy::details::is_pair<element_type>());
        }
    }

    if constexpr (!is_char_v)
        os << " }";
    return os;
}

/**
 * @brief 对 pair 实现流式输出，如果 pr.second 还是 pair，则会递归调用.
 *
 * @tparam T
 * @tparam U
 * @param[in] os
 * @param[in] pr
 * @return std::ostream&
 */
template <typename T, typename U> inline std::ostream &operator<<(std::ostream &os, const std::pair<T, U> &pr)
{
    os << '(' << pr.first << ", " << pr.second << ')';
    return os;
}

/**
 * @brief 输出 tuple 类型.
 *
 * @tparam Args
 * @param[in] os
 * @param[in] t
 * @return std::ostream&
 */
template <typename... Args> inline std::ostream &operator<<(std::ostream &os, const std::tuple<Args...> &t)
{
    os << "[";
    xy::details::print_tuple<0, sizeof...(Args), Args...>::print(os, t);
    return os << "]";
}

namespace xy
{

namespace details
{

/**
 * @brief 针对元素为键值对的类型的输出实现.
 *
 * @tparam T
 * @tparam Cont
 * @param[in] os
 * @param[in] element
 * @return decltype(std::declval<typename Cont::key_type>(), os)
 */
template <typename T, typename Cont>
inline auto output_element(std::ostream &os, const T &element, const Cont &, const std::true_type)
    -> decltype(std::declval<typename Cont::key_type>(), os)
{
    // element.second 可能还是一个 pair，则调用下面的 pair 流式输出
    os << element.first << " => " << element.second;
    return os;
}

/**
 * @brief 针对其它类型的输出实现.
 *
 * @tparam T
 * @tparam Cont
 * @param[in] os
 * @param[in] element
 * @param[in] ...
 * @return decltype(os)
 */
template <typename T, typename Cont>
inline auto output_element(std::ostream &os, const T &element, const Cont &, ...) -> decltype(os)
{
    os << element;
    return os;
}

} // namespace details

} // namespace xy
